/*
 * @Author: rock
 * @Date: 2025-04-19 11:29:55
 * @LastEditors: rock 
 * @LastEditTime: 2025-04-19 11:42:28
 * @FilePath: /007loggerlib/include/logger.hpp
 * @Description: 
 * 
 * Copyright (c) 2025 by rock, All Rights Reserved. 
 */


 #ifndef _LOGGER_H
 #define _LOGGER_H

 #include <iostream>
 #include <mutex>
 #include <condition_variable>
 #include <string>
 #include <queue>
 #include <thread>
 #include <chrono>
 #include <fstream>
 #include <sstream>
 #include <vector>
 #include <atomic>
 #include <stdexcept>
 
 /**
  * @Author: rock
  * @description: 把单个参数转化为字符串, 这里模板参数是一个万能引用
  * @param :[ T&& arg ]-> 右值引用完美转发
  * T 如果是左值, 那么arg就是左值引用
  * T 如果是右值, 那么arg就是右值引用
  * @return :[ ]
  */
 template <typename T>
 std::string to_string_helper(T &&arg)
 {
     std::ostringstream oss;
     oss << std::forward<T>(arg); // 外部调用是右值, 那这里返回右值, 外部调用是左值, 这里返回左值
     return oss.str();
 }
 
 class LogQueue
 {
 public:
     void push(const std::string &msg);
     bool pop(std::string &msg);
     void shutdown();
 
 private:
     std::queue<std::string> queue_; // 数据队列
     std::mutex mtx;                 // 保证线程安全
     std::condition_variable cond;   // 保证线程同步
     bool is_shutdown = false;       // 默认情况下关闭是false, 开启状态
 };
 
 //日志级别
 enum class LogLevel
 {
     INFO,
     DEBUG,
     WARN,
     ERROR
 };
 
 class Logger
 {
 public:
     Logger(const std::string &filename)
         : log_file_(filename, std::ios::out | std::ios::app) // 向filename文件中输入输出, 输入输出方式是输出out, 输出方式是追加的方式
           ,
           exit_flag_(false) // 默认退出标记false, 不退出
     {
         if (!log_file_.is_open())
         {
             // 如果文件没有打开, 就抛出异常
             throw std::runtime_error("Failed to open log file\n");
         }
         // 启动工作线程, 开始向log文件中写入msg
         worker_thread_ = std::thread([this]() -> void
                                      {
             std::string msg;
             while(log_queue_.pop(msg))
             {
                 log_file_ << msg << std::endl;
             } });
     }
 
     // 善后处理
     ~Logger()
     {
         exit_flag_ = true;
         log_queue_.shutdown(); // 关闭队列, 并且要通知消费者线程, 队列已经退出
         if (worker_thread_.joinable())
         {
             // 防止log线程在主线程中执行时, log线程退出, 其他子线程不能回收
             // 这里是在等待log子线程退出
             worker_thread_.join();
         }
         // 关闭文件
         if (log_file_.is_open())
         {
             log_file_.close();
         }
     }
     template <typename... Args>
     void log(LogLevel level, const std::string &format, Args &&...args)
     {
         //格式化log信息
         //日志级别提示
         std::string str_level;
         switch(level)
         {
             case LogLevel::INFO :
                 str_level = "[INFO] ";
                 break;
             case LogLevel::DEBUG :
                 str_level = "[DEBUG] ";
                 break;
             case LogLevel::WARN :
                 str_level = "[WARN] ";
                 break;
             case LogLevel::ERROR :
                 str_level = "[ERROR] ";
                 break;
                 
         }
         log_queue_.push(str_level + formatMessage(format, std::forward<Args>(args)...));
     }
 
 private:
     //获取时间戳
     std::string getCurrentTime();
 
     template <typename... Args>
     std::string formatMessage(const std::string &format, Args &&...args)
     {
         std::vector<std::string> arg_strings = {to_string_helper(std::forward<Args>(args))...};
         std::ostringstream oss;
         size_t arg_index = 0;
         size_t pos = 0;
 
         // 如果format中含有花括号, 把pos位置找到并且把{}替换为字符串
         size_t placeholder = format.find("{}", pos);
         while (placeholder != std::string::npos)
         {
             oss << format.substr(pos, placeholder - pos); // 截取, 获取长度, 写入
             if (arg_index < arg_strings.size())
             {
                 oss << arg_strings[arg_index++];
             }
             else
             {
                 // arg_strings为空时, 原字符串format不变, 仍然是原来的花括号
                 // 这里也可以直接break循环
                 oss << "{}";
             }
             // 第一个花括号匹配完
 
             // 开始匹配第二个花括号
             // 第一个花括号是由三个字符组成
             //  { } ,
             // 所以第二次开始检测的位置应该是pos+2
             pos = placeholder + 2;                // 确定起始查找位置
             placeholder = format.find("{}", pos); // 确定下一次匹配到 { 的位置
         }
 
         // 剩下的子串如果没有花括号, 或者整个子串如果没有花括号, 把子串原样拼接
         oss << format.substr(pos);
         while (arg_index < arg_strings.size())
         {
             oss << arg_strings[arg_index++];
         }
         return "["+ getCurrentTime() + "] "+oss.str();
     }
 
     LogQueue log_queue_;          // 操作队列
     std::thread worker_thread_;   // 操作线程
     std::ofstream log_file_;      // 文件输出流, 输出到文件
     std::atomic<bool> exit_flag_; // 退出标记
 };

 #endif